#pragma once

#include "evnet/http/http.hpp"
#include "evnet/http/request.hpp"
#include "evnet/http/reply.hpp"
#include "evnet/queue.hpp"

#include <evhtp.h>

#include <map>
#include <list>
#include <string>
#include <functional>

namespace evnet {
namespace http {

class server
{
public:
    typedef evhtp_t* handle_type;
	// сделано для совместимости с компилятором microsoft
    typedef std::function<void(request)> handler_type;
    typedef std::map<std::size_t, handler_type> location_type;

private:
    handle_type handle_{nullptr};

    // обработчики локейшенов
    location_type index_[htp_method_UNKNOWN];

public:

    server(server&&) = delete;
    server(const server&) = delete;
    server& operator=(const server&) = delete;

    server(queue& queue)
        : handle_(evhtp_new(queue, nullptr))
        , index_()
    {
        if (!handle_)
            throw std::runtime_error("evhtp_new");
    }

    void init_ssl(const evhtp_ssl_cfg_t& ssl)
    {
        evhtp_ssl_init(handle_, const_cast<evhtp_ssl_cfg_t*>(&ssl));
    }

    void start(const std::string& host, std::size_t port, int backlog = 1024)
    {
        // устанавливаем обработчик всех локейшенов
        evhtp_set_gencb(handle_, &server::dispatch, this);

        int result = evhtp_bind_socket(handle_, host.c_str(),
                                       static_cast<uint16_t>(port), backlog);
        if (result != 0)
            throw std::runtime_error("evhtp_bind_socket");
    }

    handle_type handle() const throw()
    {
        return handle_;
    }

private:

    void operator()(htp_method method, std::string path, request req)
    {
        auto f = index_[method].find(std::hash<std::string>()(path));
        if (f != index_[method].end())
        {
			f->second(std::move(req));
        }
        else
        {
            std::cout << request::method_name(method) << ' '
                      << path << " - " << status_code::not_found << std::endl;
            req.send(reply::not_found());
        }
    }

    static void dispatch(evhtp_request_t *req, void *ptr) throw()
    {
        try
        {
            server& srv = *static_cast<server*>(ptr);
            if (req)
            {
                if (req->uri && req->uri->path && req->uri->path->full)
                {
                    srv(evhtp_request_get_method(req),
                        std::string(req->uri->path->full),
                        request(req));
                    return;
                }
            }

            evhtp_send_reply(req, EVHTP_RES_SERVERR);
        }
        catch (const std::exception& e)
        {
            evbuffer_add(req->buffer_out, e.what(), strlen(e.what()));
            evhtp_send_reply(req, EVHTP_RES_SERVERR);
        }
    }

    void setup(const std::string& path, htp_method method, std::nullptr_t)
    {
        index_[method].erase(std::hash<std::string>()(path));
    }

    void setup(const std::string& path, htp_method method, handler_type handler)
    {
        index_[method][std::hash<std::string>()(path)] = std::move(handler);
    }

public:

    template<typename Handler>
    void get(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_GET, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void head(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_HEAD, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void post(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_POST, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void put(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_PUT, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void del(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_DELETE, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void mkcol(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_MKCOL, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void copy(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_COPY, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void move(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_MOVE, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void options(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_OPTIONS, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void propfind(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_PROPFIND, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void proppatch(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_PROPPATCH, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void lock(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_LOCK, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void unlock(const std::string& path, Handler handler)
    {
        setup(path, htp_method_UNLOCK, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void trace(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_TRACE, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void connect(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_CONNECT, std::forward<Handler>(handler));
    }

    template<typename Handler>
    void patch(const std::string& path, Handler&& handler)
    {
        setup(path, htp_method_PATCH, std::forward<Handler>(handler));
    }

    void stop()
    {
        evhtp_unbind_socket(handle_);
    }

    ~server() throw()
    {
        if (handle_)
            evhtp_free(handle_);
    }

};

} // namespace http
} // namespace evnet
